package client.net;


import java.io.IOException;
import javax.bluetooth.UUID;
import javax.media.protocol.PushSourceStream;
import javax.media.protocol.ContentDescriptor;
import javax.media.protocol.SourceTransferHandler;
import javax.media.rtp.RTPConnector;
import javax.media.rtp.OutputDataStream;

/**
 * @author primianoc
 *
 */
/**
 * An implementation of RTPConnector based on L2CAP.
 */
public class RTPL2capAdapterReceiver implements RTPConnector 
{
	/**
	 * La classe L2CapConnectionManager fornisce
	 * i metodi send e receive.
	 */
	private L2CapConnectionManager _dataConnectionManager = null;
	private L2CapConnectionManager _controlConnectionManager = null;

	/**
	 *  Inner Class che offrono i metodi read e write 
	 *  utilizzate dal framework JMF. Gli input stream 
	 *  estendono la classe Thread.
	 */
	private L2CAPInputStream _dataInputStream = null;
	private L2CAPInputStream  _controlInputStream = null;
	private L2CAPOutputStream _dataOutStream = null;
	private L2CAPOutputStream _controlOutStream = null;

	private String _dataPort = null;;

	/**
	 * @return the _dataConnectionManager
	 * Il riferimento alla connessione dati viene utilizzato per spedire al
	 * gateway una nuova posizione all'interno dello stream.
	 */

	public L2CapConnectionManager getDataConnectionManager() 
	{
		return _dataConnectionManager;
	}

	/**
	 * Costruttore con parametro porta PSM dati
	 */
	public RTPL2capAdapterReceiver(String portaDati)
	{
		_dataPort = portaDati;

		_dataConnectionManager = new L2CapConnectionManager(getUrlDataLocal());

		_controlConnectionManager = new L2CapConnectionManager(getUrlControlLocal());

	}

	private String getUrlDataLocal()
	{
		return "btl2cap://localhost:" + 
		(new UUID(0x0100)).toString() +
		";name=L2cap Server Data;bluecovepsm=" + 
		_dataPort + 
		";ReceiveMTU=1024;TransmitMTU=1024";
	}

	private String getUrlControlLocal()
	{
		/**
		 * La porta usata dal canale di controllo è la successiva
		 * di quella usata dal canale dati.
		 */
		int portaTemporanea = (Integer.parseInt(_dataPort, 16)) + 2;
		String ctrlPort = Integer.toHexString(portaTemporanea);

		return "btl2cap://localhost:" +
		(new UUID(0x0100)).toString() +
		";name=L2cap Server Control;bluecovepsm=" +
		ctrlPort;
	}


	/**
	 * Restituisce true se il canale dati e il canale di controllo sono aperti.
	 */
	public boolean isConnectionsOpened()
	{
		boolean connectionsOpened = false;

		if(_dataConnectionManager.isConnectionOpened() == true &&
				_controlConnectionManager.isConnectionOpened() == true)
		{
			connectionsOpened = true;
		}

		return connectionsOpened;
	}

	/**
	 * Returns an input stream to receive the RTP data
	 */
	public PushSourceStream getDataInputStream() throws IOException 
	{
		if (_dataInputStream == null) 
		{
			_dataInputStream = new L2CAPInputStream(_dataConnectionManager);
			_dataInputStream.setName("Data Input Stream");
			_dataInputStream.setPriority(4);
			_dataInputStream.start();
		}
		return _dataInputStream;
	}

	/**
	 * Returns an output stream to send the RTP data.
	 */
	public OutputDataStream getDataOutputStream() throws IOException 
	{
		if (_dataOutStream == null)
		{
			_dataOutStream = new L2CAPOutputStream(_dataConnectionManager);
		}
		return _dataOutStream;
	}

	/**
	 * Returns an input stream to receive the RTCP data.
	 */
	public PushSourceStream getControlInputStream() throws IOException 
	{
		if (_controlInputStream == null) 
		{
			_controlInputStream = new L2CAPInputStream(_controlConnectionManager);
			_controlInputStream.setName("Control Input Stream");
			_controlInputStream.setPriority(4);
			_controlInputStream.start();
		}
		return _controlInputStream;
	}

	/**
	 * Returns an output stream to send the RTCP data.
	 */
	public OutputDataStream getControlOutputStream() throws IOException 
	{
		if (_controlOutStream == null)
		{
			_controlOutStream = new L2CAPOutputStream(_controlConnectionManager);
		}
		return _controlOutStream;
	}

	/**
	 * Close all the RTP, RTCP streams.
	 */
	public void close() 
	{
		if (_dataInputStream != null)
		{	
			_dataInputStream.kill();
		}

		if (_controlInputStream != null)
		{
			_controlInputStream.kill();
		}
	}

	/**
	 * Set the receive buffer size of the RTP data channel.
	 * This is only a hint to the implementation.  The actual implementation
	 * may not be able to do anything to this.
	 */
	public void setReceiveBufferSize( int size) throws IOException 
	{
		// Not applicable.
	}

	/**
	 * Get the receive buffer size set on the RTP data channel.
	 * Return -1 if the receive buffer size is not applicable for 
	 * the implementation.
	 */
	public int getReceiveBufferSize() 
	{
		// Not applicable.	
		return -1;
	}

	/**
	 * Set the send buffer size of the RTP data channel.
	 * This is only a hint to the implementation.  The actual implementation
	 * may not be able to do anything to this.
	 */
	public void setSendBufferSize( int size) throws IOException 
	{
		// Not applicable.
	}

	/**
	 * Get the send buffer size set on the RTP data channel.
	 * Return -1 if the send buffer size is not applicable for 
	 * the implementation.
	 */
	public int getSendBufferSize() 
	{
		// Not applicable.
		return -1;
	}

	/**
	 * Return the RTCP bandwidth fraction.  This value is used to
	 * initialize the RTPManager.  Check RTPManager for more details.
	 * Return -1 to use the default values.
	 */
	public double getRTCPBandwidthFraction() 
	{
		return -1;
	}

	/**
	 * Return the RTCP sender bandwidth fraction.  This value is used to
	 * initialize the RTPManager.  Check RTPManager for more detauls.
	 * Return -1 to use the default values.
	 */
	public double getRTCPSenderBandwidthFraction() 
	{
		return -1;
	}

	/**
	 * An inner class to implement an OutputDataStream based on L2CAP connection.
	 */
	class L2CAPOutputStream implements OutputDataStream 
	{
		private L2CapConnectionManager _l2CapConnectionManager = null;

		public L2CAPOutputStream(L2CapConnectionManager l2CapConnectionManager)
		{
			_l2CapConnectionManager = l2CapConnectionManager;
		}

		/**
		 * Il metodo write permette di inviare un array di len byte tramite 
		 * la connessione L2CAP. Se la connessione non è stata stabilita il 
		 * thread si sospende invocando il metodo wait sull'oggetto 
		 * ConnectionMonitor.
		 */
		public int write(byte data[], int offset, int len) 
		{

			int byteSent = _l2CapConnectionManager.send(data, true);

			return byteSent;

		}

	}


	/**
	 * An inner class to implement an PushSourceStream based on L2CAP connection.
	 */
	class L2CAPInputStream extends Thread implements PushSourceStream 
	{
		private boolean _done = false;
		private boolean _dataRead = false;
		private SourceTransferHandler _sourceTransferHandler = null;
		private L2CapConnectionManager _l2CapConnectionManager = null;


		public L2CAPInputStream(L2CapConnectionManager l2CapConnectionManager)
		{
			_l2CapConnectionManager = l2CapConnectionManager;
		}

		/**
		 * Il metodo read permette di ricevere un array di len byte dalla connessione L2CAP.
		 */
		public int read(byte buffer[], int offset, int length)
		{
			int byteReceived = -1;
			try
			{
				byteReceived = _l2CapConnectionManager.receive(buffer);
			}
			catch (InterruptedException e)
			{
				e.printStackTrace();
			}

			synchronized (this) 
			{
				_dataRead = true;

				notify();
			}

			return byteReceived;
		}

		/**
		 * Metodo eseguito prima di restituire l'istanza.
		 *  Richiama il metodo super.start()
		 * ossia il metodo che provoca l'esecuzione del metodo run(). 
		 */
		public synchronized void start() 
		{
			super.start();

			if (_sourceTransferHandler != null) 
			{
				_dataRead = true;

				notify();
			}
		}

		/**
		 * Metodo richiamato dal metodo close() dell'oggetto RTPL2CAPAdapterReceiver.
		 * Consente di chiudere la connessione e di terminare il thread.
		 */
		public synchronized void kill() 
		{
			_done = true;

			notify();

			this.interrupt();

			_l2CapConnectionManager.close();
		}

		/* Determine the size of the buffer needed for the data transfer. 
		 * This method is provided so that a transfer handler can determine how 
		 * much data, at a minimum, will be available to transfer from the source.
		 *  Overflow and data loss is likely to occur if this much data isn't read 
		 *  at transfer time.
	        Returns:
	           The size of the data transfer.
		 */
		public int getMinimumTransferSize() 
		{		
			/**
			 * Metodo invocato prima di invocare read. Il valore restituito 
			 * serve per inizializzare l'array di byte che conterrà i dati
			 * ricevuti. Il valore più grande misurato è 676. Imposto
			 * un valore pari a 1024.
			 */
			return 1024;	
		}

		public synchronized void setTransferHandler(SourceTransferHandler sourceTransferHandler) 
		{
			_sourceTransferHandler = sourceTransferHandler;
			_dataRead = true;
			notify();
		}

		// Not applicable.
		public ContentDescriptor getContentDescriptor() 
		{
			return null;
		}

		// Not applicable.
		public long getContentLength() 
		{
			return LENGTH_UNKNOWN;
		}

		// Not applicable.
		public boolean endOfStream() 
		{
			return false;
		}

		// Not applicable.
		public Object[] getControls() 
		{
			return new Object[0];
		}

		// Not applicable.
		public Object getControl(String type) 
		{
			return null;
		}

		/**
		 * Loop and notify the transfer handler of new data.
		 */
		public void run() 
		{
			while (_done == false) 
			{
				synchronized (this) 
				{
					while (_dataRead == false && _done == false) 
					{
						try 
						{
							wait();
						} 
						catch (InterruptedException e) 
						{ 
							e.printStackTrace();
						}
					}

					_dataRead = false;
				}

				if (_sourceTransferHandler != null && _done == false) 
				{
					_sourceTransferHandler.transferData(this);
				}
			}
		}

	}
}

